package com.dayatang.hrm.compensation.domain;

import java.util.Date;

import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;

import com.dayatang.commons.domain.AbstractEntity;
import com.dayatang.commons.domain.DataType;
import com.dayatang.commons.domain.InstanceFactory;
import com.dayatang.hrm.compensation.context.CompensationMessageSource;
import com.dayatang.hrm.compensation.domain.exception.CompensationRuntimeException;
import com.dayatang.hrm.compensation.policy.RoundingModePolicy;
import com.dayatang.hrm.compensation.utils.DateVersionUtils;
import com.dayatang.hrm.compensation.vo.NumberScale;
import com.dayatang.utils.DateUtils;

/**
 * 薪资项目的设置
 * 
 * @author chencao
 * 
 */
@Entity
@Table(name = "pay_salary_cal_item_setting")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "item_setting_category", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
@NamedQueries( {
		@NamedQuery(name = "SalaryCalculateItemSetting.get", query = "select setting from SalaryCalculateItemSetting setting inner join setting.salaryCalItem o where (o = :pCalItem and setting.fromDate <= :pQueryDate and setting.toDate >= :pQueryDate)"),
		@NamedQuery(name = "SalaryCalculateItemSetting.isNameAlreadyUsedInSameSalaryAccountSuiteExceptSelf.selfIsNull", query = "select setting from SalaryCalculateItemSetting setting inner join setting.salaryCalItem o inner join o.salaryAccountSuite suite where (suite = :pSuite and o.abolishDate >= :pFromDate and  ((setting.fromDate <= :pToDate and setting.toDate >= :pToDate ) or (setting.toDate >= :pFromDate and setting.toDate <= :pToDate)))"),
		@NamedQuery(name = "SalaryCalculateItemSetting.isNameAlreadyUsedInSameSalaryAccountSuiteExceptSelf.selfIsExist", query = "select setting from SalaryCalculateItemSetting setting inner join setting.salaryCalItem o inner join o.salaryAccountSuite suite where (suite = :pSuite and o.abolishDate >= :pFromDate and setting <> :pSelf and  ((setting.fromDate <= :pToDate and setting.toDate >= :pToDate ) or (setting.toDate >= :pFromDate and setting.toDate <= :pToDate)))") })
public class SalaryCalculateItemSetting extends AbstractEntity {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6603899697107929316L;

	private static final Logger logger = LoggerFactory
			.getLogger(SalaryCalculateItemSetting.class);

	private static SalaryCalculateItemSettingRepository salaryCalItemSettingRepository;

	protected static MessageSourceAccessor messages = CompensationMessageSource
			.getAccessor();

	// 薪资计算项目名称
	@Column(name = "name", nullable = false)
	private String name;
	@Column(name = "is_need_tag_version")
	private Boolean needTagVersion;
	// 数据类型
	@Column(name = "datatype")
	private DataType dataType;
	// 排序号
	@Column(name = "order_seq")
	private Integer orderSeq;
	// 层次
	@Column(name = "item_level")
	private Integer level;
	// 进位策略
	@ManyToOne
	@JoinColumn(name = "rounding_mode_policy_id")
	private RoundingModePolicy roundingModePolicy;
	// 计算精度
	@Embedded
	@AttributeOverride(name = "digitAmount", column = @Column(name = "calculate_scale"))
	private NumberScale calculateScale;
	// 显示精度
	@Embedded
	@AttributeOverride(name = "digitAmount", column = @Column(name = "display_scale"))
	private NumberScale displayScale;
	// 备注
	@Column(name = "remark", length = 200)
	private String remark;

	@Column(name = "from_date", nullable = false)
	@Temporal(TemporalType.TIMESTAMP)
	private Date fromDate;

	@Column(name = "to_date")
	@Temporal(TemporalType.TIMESTAMP)
	private Date toDate = DateUtils.MAX_DATE;

	// 创建时间
	@Column(name = "created_time")
	@Temporal(TemporalType.TIMESTAMP)
	private Date created;

	@ManyToOne
	@JoinColumn(name = "salary_item_type_id")
	private SalaryItemType salaryItemType;

	@ManyToOne
	@JoinColumn(name = "salary_cal_item_id")
	private SalaryCalculateItem salaryCalItem;

	public SalaryCalculateItemSetting() {
	}

	public SalaryCalculateItemSetting(SalaryCalculateItem calItem) {
		this.salaryCalItem = calItem;
	}

	public static SalaryCalculateItemSettingRepository getSalaryCalItemSettingRepository() {
		if (salaryCalItemSettingRepository == null) {
			salaryCalItemSettingRepository = InstanceFactory
					.getInstance(SalaryCalculateItemSettingRepository.class);
		}
		return salaryCalItemSettingRepository;
	}

	public static void setSalaryCalItemSettingRepository(
			SalaryCalculateItemSettingRepository salaryCalItemSettingRepository) {
		SalaryCalculateItemSetting.salaryCalItemSettingRepository = salaryCalItemSettingRepository;
	}

	public static MessageSourceAccessor getMessages() {
		return messages;
	}

	public static void setMessages(MessageSourceAccessor messages) {
		SalaryCalculateItemSetting.messages = messages;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Boolean isNeedTagVersion() {
		return needTagVersion;
	}

	public Boolean getNeedTagVersion() {
		return needTagVersion;
	}

	public void setNeedTagVersion(Boolean needTagVersion) {
		this.needTagVersion = needTagVersion;
	}

	public DataType getDataType() {
		return dataType;
	}

	public void setDataType(DataType dataType) {
		this.dataType = dataType;
	}

	public Integer getOrderSeq() {
		return orderSeq;
	}

	public void setOrderSeq(Integer orderSeq) {
		this.orderSeq = orderSeq;
	}

	public Integer getLevel() {
		return level;
	}

	public void setLevel(Integer level) {
		this.level = level;
	}

	public RoundingModePolicy getRoundingModePolicy() {
		return roundingModePolicy;
	}

	public void setRoundingModePolicy(RoundingModePolicy roundingModePolicy) {
		this.roundingModePolicy = roundingModePolicy;
	}

	public NumberScale getCalculateScale() {
		return calculateScale;
	}

	public void setCalculateScale(NumberScale calculateScale) {
		this.calculateScale = calculateScale;
	}

	public NumberScale getDisplayScale() {
		return displayScale;
	}

	public void setDisplayScale(NumberScale displayScale) {
		this.displayScale = displayScale;
	}

	public String getRemark() {
		return remark;
	}

	public void setRemark(String remark) {
		this.remark = remark;
	}

	public SalaryCalculateItem getSalaryCalItem() {
		return salaryCalItem;
	}

	public void setSalaryCalItem(SalaryCalculateItem calItem) {
		this.salaryCalItem = calItem;
	}

	public Date getFromDate() {
		return fromDate;
	}

	public void setFromDate(Date fromDate) {
		this.fromDate = fromDate;
	}

	public Date getToDate() {
		return toDate;
	}

	public void setToDate(Date toDate) {
		this.toDate = toDate;
	}

	public Date getCreated() {
		return created;
	}

	public void setCreated(Date created) {
		this.created = created;
	}

	public SalaryItemType getSalaryItemType() {
		return salaryItemType;
	}

	public void setSalaryItemType(SalaryItemType salaryItemType) {
		this.salaryItemType = salaryItemType;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof SalaryCalculateItemSetting == false) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		SalaryCalculateItemSetting rhs = (SalaryCalculateItemSetting) obj;
		return new EqualsBuilder().append(getId(), rhs.getId()).isEquals();
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(17, 37).append(getId()).toHashCode();
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this,
				ToStringStyle.SHORT_PREFIX_STYLE);
	}

	/*
	 * =======================================
	 * 
	 * action
	 * 
	 * ========================================
	 */

	private void save() {

		if (isNew()) {
			setCreated(new Date());

			if (fromDate == null) {
				setFromDate(created);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("准备新增设置：name【{}】，fromDate【{}】，toDate【{}】",
						new Object[] { name, fromDate, toDate });
			}

			validateName();
			getSalaryCalItemSettingRepository().save(this);
		} else {
			if (isNeedTagVersion()) {
				Date markedDate = new Date();

				// 取出原有设置
				SalaryCalculateItemSetting setting = getPure(this);
				// 设置结束日期（MySQL不能精确到毫秒）
				setting.setToDate(DateVersionUtils
						.getPreviousVersionTimestamp(markedDate));
				// 不需要验证名称
				// validateName();
				if (logger.isDebugEnabled()) {
					logger
							.debug("准备备份原有设置：name【{}】，fromDate【{}】，toDate【{}】",
									new Object[] { setting.getName(),
											setting.getFromDate(),
											setting.getToDate() });
				}
				getSalaryCalItemSettingRepository().save(setting);

				// 作为新对象保存
				SalaryCalculateItemSetting newInstance = new SalaryCalculateItemSetting(
						salaryCalItem);
				BeanUtils.copyProperties(this, newInstance);
				newInstance.setId(null);
				newInstance.setFromDate(null);
				newInstance.setVersion(0);
				newInstance.setCreated(DateVersionUtils
						.getCurrentlyVersionTimestamp(markedDate));
				newInstance.save();
			} else {
				validateName();
				getSalaryCalItemSettingRepository().save(this);
			}
		}
	}

	public void update() {
		if (isNew()) {
			throw new CompensationRuntimeException(messages.getMessage(
					"被更新的薪资计算项目设置不存在。",
					"SalaryCalculateItemSetting.update.entityNotExist"));
		}
		validateName();
		getSalaryCalItemSettingRepository().save(this);
	}

	/**
	 * 改正设置并保存（不会将设置备份）
	 */
	public void saveChanges() {
		// setNeedTagVersion(false);
		save();
	}

	/**
	 * 备份原有设置并保存当前设置（会将之前的设置备份）
	 */
	public void archive() {
		setNeedTagVersion(true);
		save();
	}

	public void markArchive() {
		setNeedTagVersion(true);
		update();
	}

	public void abolish(Date abolishDate) {
		if (abolishDate == null) {
			abolishDate = new Date();
		}
		setToDate(abolishDate);
		save();
	}

	public void validateName() {
		Assert.notNull(name, messages.getMessage(
				"SalaryCalculateItemSetting.validateName.nameIsNull",
				"薪资计算项目的名称不允许为空。"));

		if (isNameAlreadyUsedInSameSalaryAccountSuite()) {
			throw new CompensationRuntimeException(messages.getMessage(
					"SalaryCalculateItemSetting.validateName.nameAlreadyUsed",
					"该薪资计算项目的名称已经被使用。"));
		}
	}

	public boolean isNameAlreadyUsedInSameSalaryAccountSuite() {
		if (isNew()) {
			return getSalaryCalItemSettingRepository()
					.isNameAlreadyUsedInSameSalaryAccountSuiteExceptSelf(
							salaryCalItem.getName(),
							salaryCalItem.getSalaryAccountSuite(), fromDate,
							toDate, null);
		} else {
			return getSalaryCalItemSettingRepository()
					.isNameAlreadyUsedInSameSalaryAccountSuiteExceptSelf(
							salaryCalItem.getName(),
							salaryCalItem.getSalaryAccountSuite(), fromDate,
							toDate, this);
		}
	}

	public static SalaryCalculateItemSetting init(SalaryCalculateItem calItem) {
		return new SalaryCalculateItemSetting(calItem);
	}

	public static SalaryCalculateItemSetting get(Long id) {
		return getSalaryCalItemSettingRepository().get(id);
	}

	public static SalaryCalculateItemSetting get(SalaryCalculateItem calItem,
			Date queryDate) {
		return getSalaryCalItemSettingRepository().get(calItem, queryDate);
	}

	public static SalaryCalculateItemSetting getPure(
			SalaryCalculateItemSetting salaryCalculateItemSetting) {
		return getSalaryCalItemSettingRepository().getPure(
				salaryCalculateItemSetting);
	}

}
